/*
 * A simple CORBA banking application
 * Part of the GICOM pedagogical project
 */
package gicom.bank;

import gicom.PersistentObject;
import gicom.generated.bankServices.Address;
import gicom.generated.bankServices.BankOperations;
import gicom.generated.bankServices.BankPOATie;
import gicom.generated.bankServices.Branch;
import gicom.generated.bankServices.BranchHelper;
import gicom.generated.bankServices.BranchStruct;
import gicom.generated.bankServices.ProblemException;
import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.omg.CORBA.ORB;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.Servant;

/**
 * The transient Bank implementation
 */

public class BankImpl extends PersistentObject implements BankOperations,BankImplMBean {

    /**
     * The bank address
     */
    private Address addr;
    /**
     * The bank name
     */
    private String name = null;
    /**
     * The bank number
     */
    private String number = null;
    /**
     * The bank's branches
     */
    private transient Map<String, Branch> branches = null;

    private static Logger logger = Logger.getLogger(BankImpl.class.getName());
    
    public BankImpl() {
        
    }
    
    /**
     * Create a bank
     * 
     * @param name
     *                the bank name
     * @param number
     *                the bank number
     * @param address
     *                the bank address
     */
    public BankImpl(String name, String number, Address address) {
	super(name);
	this.name = name;
	this.addr = address;
	this.number = number;
	this.branches = new HashMap<String, Branch>();

    }

    /**
     * Get the bank address
     * 
     * @return the bank address
     */
    public Address addr() {
	return this.addr;
    }

    /**
     * Get the bank name
     * 
     * @return the bank name
     */
    public String name() {
	return this.name;
    }

    /**
     * Get the bank number
     * 
     * @return the bank number
     */
    public String number() {
	return this.number;
    }

    /**
     * Find all branches of the bank
     * 
     * @return all branches
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public BranchStruct[] findAllBranches() {
	if (branches == null) {
	    logger.log(Level.SEVERE, "Branches est null !!");
	    return null;
	}
	Iterator<Map.Entry<String, Branch>> iterator = branches.entrySet().iterator();
	BranchStruct[] branchStructs = new BranchStruct[branches.size()];
	System.out.println("Nombre de branch : " + branches.size());
	int i = 0;
	while (iterator.hasNext()) {
	    Map.Entry<String, Branch> entry = (Map.Entry<String, Branch>) iterator.next();
	    String number = entry.getKey();
	    Branch branch = getBranchReference(number);
	    if (branch != null)
		branchStructs[i++] = new BranchStruct(number, branch);
	}
	//System.out.println(branchStructs.length);
	return branchStructs;
    }

    /**
     * Find a specific branch
     * 
     * @param number
     *                the bank number
     * @return the branch's reference
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public Branch findBranch(String number) throws ProblemException {
	if (branches.containsKey(number))
	    return (Branch) branches.get(number);
	else
	    throw new ProblemException("Branch number error", "This branch number doesn't exist in this bank");

    }

    /**
     * Register a branch
     * 
     * @param number
     *                the bank number
     * @param b
     *                the branch's reference
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public void registerBranch(String number, Branch b) throws ProblemException {
	try {
            branches.put(number, b);
            this.save();
            System.out.println("Branch " + b.name() + " registered.");
        } catch (Exception ex) {
            throw new ProblemException("error while saving the Branch", ex.getMessage());
        }
    }

    /**
     * Unregister a branch
     * 
     * @param number
     *                the bank number
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public void unregisterBranch(String number) throws ProblemException {
        try {
            Branch b = branches.remove(number);
            this.save();
            System.out.println("Branch " + b.name() + " unregistered.");
        } catch (Exception ex) {
            throw new ProblemException("error while saving the Branch", ex.getMessage());
        }
    }

    public Branch getBranchReference(String number) {
	return branches.get(number);
    }

    /*
     * Persistence
     */
    private transient List<String> branchesTmp = null;

    @Override
    public void doReload(ORB orb, POA poa) throws Exception {
	logger.info("Reinitialisation de la Bank numero : " + this.number);
        Iterator<String> iter = branchesTmp.iterator();

        branches = new HashMap<String, Branch>();

        while (iter.hasNext()) {
            org.omg.CORBA.Object o = orb.string_to_object(iter.next());
            Branch b = BranchHelper.narrow(o);
            branches.put(b.name(), b);
        }
        branchesTmp = null;
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException, EOFException {
	// initialize branchesTmp
	branchesTmp = new ArrayList<String>();

	// addr
	this.addr = (Address) in.readObject();

	// name
	this.name = in.readUTF();

	// number
	this.number = in.readUTF();

	// id
	this.id = in.readUTF();

	// Branches ...

	int count = in.readInt();
	System.out.println("count : " + count);
	for (int i = 0; i < count; i++) {
	    branchesTmp.add(in.readUTF());
	}

    }

    public void writeExternal(ObjectOutput out) throws IOException {

	// addr
	out.writeObject(this.addr);

	// name
	out.writeUTF(this.name);

	// number
	out.writeUTF(this.number);

	// id
	out.writeUTF(this.id);

	// Branches

	// number of branches
	out.writeInt(this.branches.size());
	// for each branch
	for (Branch b : branches.values()) {
            try {
                String s = getORB().object_to_string(b);
                out.writeUTF(s);
            } catch (Exception ex) {
                throw new IOException("Error while writing the branch.", ex);
            }
	}
    }

    public Servant createServant() {
	return new BankPOATie(this);
    }

    @Override
    public void desactivate() throws ProblemException {
        try {
            getPOA().deactivate_object(getId().getBytes());
        } catch (Exception ex) {
            throw new ProblemException("error while desactivating object", ex.getMessage());
        }
    }

    public String getName() {
	return this.name;
    }

    public String getAddress() {
	return addr.street+", "+addr.city+" ("+addr.telephone+")";
    }

    public String getNumber() {
	return this.number;
    }
    
    public String toString() {
	return this.name;
    }
}
